//
// Created by benxb on 2021/11/29.
//

#ifndef BENXDB_DATA_TABLE_MANAGER_H
#define BENXDB_DATA_TABLE_MANAGER_H

#include <mutex>
#include <condition_variable>
#include <unordered_set>
#include <deque>
#include <storage/data_chunk_io.h>
#include <thread>
#include "mem/mem_data_table.h"
#include <catelog/query.h>
#include <type/type.h>
#include <catelog/point_cloud.h>
#include <catelog/point_type.h>
#include <index/linear_index.h>

namespace benxdb {

class DataTableManager {
public:
    explicit DataTableManager(const std::shared_ptr<Table>& table, uint32_t table_count=2);
    ~DataTableManager();

    /**
     * Append new tuple into active table
     * @param tuple
     * @return true if success
     */
    bool AppendTuple(Tuple& tuple);

    /**
     * Append many tuple into active table
     * @param tuple
     * @return true if success
     */
    bool AppendManyTuple(std::vector<Tuple>& tuple);

    bool AppendPoint(PointXYZI& point);

    template<class PointT>
    bool AppendPointCloud(PointCloud<PointT>& point_cloud) {
        timer_.Start();
        MemDataTable* table = GetActiveMemTable();
        int total_size = point_cloud.Size();
        int next_append = 0;
        while (next_append < total_size) {
            int rest_count = total_size-next_append;
            int free_count = table->GetFreeRecordSlot();
            int append_size = free_count > rest_count ? rest_count : free_count;
            bool is_last_chunk = free_count >= rest_count;
            table->AppendPointCloud(point_cloud, next_append, next_append+append_size-1, is_last_chunk);
            if (!table->HasFreeRecordSlot()) {
                FreezeMemTable(table);
                table = GetActiveMemTable();
            }
            next_append += append_size;
        }
        timer_.PauseAndPrint("Manager Append records.");
    }

    bool DeleteTuple(Tuple& tuple);

    void Query(const QueryPhrase& query, std::vector<Tuple>& res);

    /**
     * query point cloud
     * @param query query phrase
     * @param res result point cloud
     * @return true if query successful
     */
    void QueryPointCloud(PointCloudQuery& query, PointCloud<PointXYZ>& res);

    friend void FlushTable(DataTableManager* me);

    std::shared_ptr<Table> GetTable() {return table_;}

private:
    std::shared_ptr<Table> table_;
    // active_table is data_table which is storing now
    MemDataTable* active_table_{};
    // data_chunk io
    std::shared_ptr<DataChunkIO> chunk_io_;

    // table queue is active_data_table cache
    std::deque<MemDataTable*> free_tables_;
    // tables full of record
    std::deque<MemDataTable*> frozen_tables_;
    std::mutex close_mtx_;
    std::mutex free_queue_mtx_;
    std::mutex frozen_queue_mtx_;
    std::condition_variable free_queue_cv_;
    std::condition_variable flush_cv_;

    std::shared_ptr<std::thread> flush_thread_;

    std::unordered_map<std::string, std::shared_ptr<LinearIndex>> linear_indices_;

    void QueryDiskChunk(QueryPhrase query, std::vector<Tuple>& res);

    /**
     * Get active memory table
     * @return active mem table
     */
    MemDataTable* GetActiveMemTable();

    /**
     * change active table to read only mem table
     * @param table active table
     */
    void FreezeMemTable(MemDataTable* table);

    void InsertMemTable(MemDataTable* mem_table);

    void FreezeActiveMemTable();

    Timer timer_;

    void QueryPointCloudFromDiskChunk(PointCloudQuery &query, PointCloud <PointXYZ> &res);
};

}


#endif //BENXDB_DATA_TABLE_MANAGER_H
